﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

using HappyTemplate.BuiltInFunctions;

namespace HappyTemplate
{
	public class TemplateContext
	{
		public const string NullText = "";
		private int _maximumScopeStackSize = 1024;

		Dictionary<string, BuiltInFunctionBase> _builtInFunctions;

		readonly List<Dictionary<string, object>> _scopeStack = new List<Dictionary<string, object>>();

		private readonly TemplateSet _templateSet;

		public TemplateSet TemplateSet { get { return _templateSet; } }
		public int MaximumScopeStackSize { get { return _maximumScopeStackSize; } set { _maximumScopeStackSize = value; } }

		public int StackSize
		{
			get { return _scopeStack.Count; }
		}

		public TemplateContext(TemplateSet templateSet, Dictionary<string, BuiltInFunctionBase> builtins)
		{
			_builtInFunctions = builtins;
			_templateSet = templateSet;

			_scopeStack.Add(new Dictionary<string, object>());
		}

		public void Push(SourceLocation from)
		{
			_scopeStack.Add(new Dictionary<string, object>());
			if (_scopeStack.Count > _maximumScopeStackSize)
				TemplateExecutionException.ThrowMaximumStackSizeExceeded(from);

		}

		public void Pop()
		{
			_scopeStack.RemoveAt(_scopeStack.Count - 1);
		}

		private bool TrySearchStack(string key, out object retval)
		{
			for (int i = _scopeStack.Count - 1; i >= 0; --i)
				if (_scopeStack[i].TryGetValue(key, out retval))
					return true;

			retval = null;
			return false;
		}

		public bool ExistsAtTop(string identText)
		{
			return _scopeStack[_scopeStack.Count - 1].ContainsKey(identText);
		}

		public bool ExistsInStack(string identText)
		{
			for (int i = _scopeStack.Count - 1; i >= 0; --i)
				if (_scopeStack[i].ContainsKey(identText))
					return true;

			return false;
		}

		public object this[string key]
		{
			get
			{
				object retval;
				if (!TrySearchStack(key, out retval))
					throw new ArgumentOutOfRangeException(String.Format("Key \"{0}\" not found in template context", key));

				return retval;
			}
			set
			{

				_scopeStack[_scopeStack.Count - 1][key] = value;
			}
		}

		internal object FindVariable(Identifier key)
		{
			object retval;

			if (!TrySearchStack(key.Parts[0], out retval))
				TemplateExecutionException.ThrowIdentifierNotFound(key);

			if (key.Parts.Length > 1)
			{
				for (int i = 1; i < key.Parts.Length; ++i)
				{
					try
					{
						if(!TryGetProperty(retval, key.Parts[i], out retval))
							TemplateExecutionException.ThrowMultipartIdentiferNotFound(key, key.Parts[i]);

						if(retval == null)
							break;
					}
					catch(Exception ex)
					{
						if (typeof(TemplateExecutionException) == ex.GetType())
							throw;

						TemplateExecutionException.
							ThrowExceptionWhileRetrievingPropertyValue(key, key.Parts[i], ex);
					}
				}
			}

			return retval;
		}

		internal BuiltInFunctionBase GetBuiltInFunction(string name)
		{
			BuiltInFunctionBase retval;
			return _builtInFunctions.TryGetValue(name, out retval) ? retval : null;
		}

		private static bool TryGetProperty(object obj, string s, out object retval )
		{
			TemplateObject templateObject = obj as TemplateObject;
			if(templateObject != null)
				return templateObject.TryGetValue(s, out retval);

			Type objectType = obj.GetType();
			PropertyInfo pi = objectType.GetProperty(s);
			if (pi == null)
			{
				retval = null;
				return false;
			}

			retval = pi.GetValue(obj, null);

			return true;
		}

		public void SetGlobal(string name, object value)
		{
			_scopeStack[0][name] = value;
		}
	}
}
